﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using AAA.Chart.Data;
using AAA.Chart.Indicator;
using System.Drawing.Drawing2D;
using AAA.Meta.Quote;

namespace AAA.Chart.Component
{

    public partial class Chart : UserControl, IChartMeta
    {
        private static readonly int DEFAULT_PAD = 30;
        private static readonly int DEFAULT_INTERVAL = 20;
        private static readonly int MIN_X_INTERVAL = 1;
        private static readonly int DEFAULT_X_SCALE = 1;
        
        // Axis Setting
        private const int X = 0;
        private const int X2 = 1;
        private const int Y = 2;
        private const int Y2 = 3;

        // Pad Setting
        private int _iLeftPad = DEFAULT_PAD * 0;
        private int _iTopPad = DEFAULT_PAD * 0;
        private int _iRightPad = DEFAULT_PAD * 2;
        private int _iBottomPad = DEFAULT_PAD;

        // Chart Meta Data
        private double _dXMin = 0;
        private double _dXMax = 100;
        private double _dX2Min = 0;
        private double _dX2Max = 100;
        private double _dYMin = 0;
        private double _dYMax = 500;
        private double _dY2Min = 0;
        private double _dY2Max = 500;        
        private IDataSource _dsDataSource;
        private List<IIndicator> _lstIndicator;

        private Color _cBorderColor = Color.White;
        private Color _cAxisLineColor = Color.White;
        private Color _cHourLineColor = Color.White;
        private Color _cDayLineColor = Color.White;
        private bool _isShowXAxis = true;
        private bool _isShowHourLine = false;
        private bool _isShowDayLine = false;
        private double _dPixelValue;
        private double _dMinValue;
        private int _iXScale;
        private List<ConstantLine> _lstCustomLine;

        private double[] _dValues;

        private int _iStartIndex = -1;
        private int _iEndIndex = -1;

        public Chart()
        {
            InitializeComponent();
            BackColor = Color.Black;            
            _lstIndicator = new List<IIndicator>();
            _lstCustomLine = new List<ConstantLine>();
            _iXScale = DEFAULT_X_SCALE;
            MouseMove += new MouseEventHandler(OnMouseMove);

/*
            VisibleChanged += new EventHandler(Chart_VisibleChanged);
            Paint += new PaintEventHandler(Chart_Paint);
            Validated += new EventHandler(Chart_Validated);
            Resize += new EventHandler(Chart_Resize);
 */ 
        }

        void OnMouseMove(object sender, MouseEventArgs e)
        {
            try
            {
                _dValues = PixelToValue(e.X, e.Y);
                PaintChart();
            }
            catch { }
        }

        protected override void  OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            PaintChart();
        }

        void Chart_Resize(object sender, EventArgs e)
        {
            try
            {
                PaintChart();
                this.Invalidate();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message + "," + ex.StackTrace);
            }
        }
        
        void Chart_Validated(object sender, EventArgs e)
        {
            PaintChart();
            this.Invalidate();
        }

        void Chart_Paint(object sender, PaintEventArgs e)
        {
            PaintChart();
        }

        void Chart_VisibleChanged(object sender, EventArgs e)
        {
            PaintChart();
            this.Invalidate();
        }

        public int StartIndex
        {
            get { return _iStartIndex; }
            set { _iStartIndex = value; }
        }

        public int EndIndex
        {
            get { return _iEndIndex; }
            set { _iEndIndex = value; }
        }

        public void Clear()
        {
            YMax = -float.MaxValue;
            YMin = float.MaxValue;
        }

        public void AddCustomLine(ConstantLine customLine)
        {
            if(_lstCustomLine.Contains(customLine) == false)
                _lstCustomLine.Add(customLine);
        }

        public void ShowHourLine(bool isShowHourLine, Color cHourLineColor)
        {
            IsShowHourLine = isShowHourLine;
            _cHourLineColor = cHourLineColor;
        }

        public void ShowDayLine(bool isShowDayLine, Color cDayLineColor)
        {
            IsShowDayLine = isShowDayLine;
            _cDayLineColor = cDayLineColor;
        }

        public int XScale
        {
            get { return _iXScale; }
            set { _iXScale = Math.Max(1, value); }
        }

        public int LeftPad
        {
            get { return _iLeftPad; }
            set { _iLeftPad = value < DEFAULT_PAD ? DEFAULT_PAD : value;  }
        }

        public int TopPad
        {
            get { return _iTopPad; }
            set { _iTopPad = value < DEFAULT_PAD ? DEFAULT_PAD : value; }
        }

        public int RightPad
        {
            get { return _iRightPad; }
            set { _iRightPad = value < DEFAULT_PAD ? DEFAULT_PAD : value; }
        }

        public int BottomPad
        {
            get { return _iBottomPad; }
            set { _iBottomPad = value < DEFAULT_PAD ? DEFAULT_PAD : value; }
        }

        public int XInterval
        {
            get { return _iXScale * MIN_X_INTERVAL; }
        }

        public double XMin
        {
            get { return _dXMin; }
            set { _dXMin = value; }
        }

        public double XMax
        {
            get { return _dXMax; }
            set { _dXMax = value; }
        }

        public double YMin
        {
            get { return _dYMin; }
            set { _dYMin = value; }
        }

        public double YMax
        {
            get { return _dYMax; }
            set { _dYMax = value; }
        }

        public Color BorderColor
        {
            get { return _cBorderColor; }
            set { _cBorderColor = value; }
        }

        public Color AxisLineColor
        {
            get { return _cAxisLineColor; }
            set { _cAxisLineColor = value; }
        }

        public bool ShowXAxis
        {
            get { return _isShowXAxis; }
            set { _isShowXAxis = value; }
        }


        public bool IsShowHourLine
        {
            get { return _isShowHourLine; }
            set { _isShowHourLine = value; }
        }

        public bool IsShowDayLine
        {
            get { return _isShowDayLine; }
            set { _isShowDayLine = value; }
        }

        private double CalculateInterval(double dMax, double dMin, double dCount)
        {
            double dInterval = 0;
            int iLength;

            dInterval = (dMax - dMin) / dCount;
            iLength = ((int)Math.Floor(dInterval)).ToString().Length;

            if (dInterval > 1)
            {
                dInterval = Math.Ceiling(dInterval / Math.Pow(10, iLength - 1)) * Math.Pow(10, iLength - 1);
            }
            else
            {
            }

            return dInterval;
        }

        public int[] ValueToPixel(double dX, double dY)
        {
            return ValueToPixel(X, Y, dX, dY);
        }

        public int[] ValueToPixel(int iXAxis, int iYAxis, double dX, double dY)
        {
            int[] iReturn = new int[2];
            //int iDataLen = _dsDataSource.GetLength(_lstIndicator[0].BaseStudy);
            int iDataLen = EndIndex - StartIndex + 1;
            //iReturn[0] = (int)((iDataLen - dX - 1) * XInterval) + LeftPad;

            switch (iXAxis)
            {
                case X:
                    iReturn[0] = (int)((dX - StartIndex) * XInterval) + LeftPad;
                    break;
                case X2:
                    
                    break;
            }

            switch(iYAxis)
            {
                case Y:
                    iReturn[1] = DrawHeight - (int)((dY - _dMinValue) / _dPixelValue);
                    break;
                case Y2:
                    
                    break;
            }
            return iReturn;
        }

        public double[] PixelToValue(int iX, int iY)
        {
            return PixelToValue(X, Y, iX, iY);
        }

        public double[] PixelToValue(int iXAxis, int iYAxis, int iX, int iY)
        {
            double[] dReturn = new double[2];

            switch (iXAxis)
            {
                case X:
                    dReturn[0] = ((double)(iX - LeftPad) / (double)XInterval) + StartIndex;
                    break;
                case X2:
                    break;
            }

            switch (iYAxis)
            {
                case Y:
                    dReturn[1] = (Height - _iBottomPad - iY) * _dPixelValue + _dMinValue;
                    break;
                case Y2:
                    break;
            }

            return dReturn;
        }

        public int DrawHeight
        {
            get { return ShowXAxis ? Height - BottomPad - TopPad: Height - TopPad; }
        }

        public int DrawWidth
        {
            get {return Width - RightPad - LeftPad;}
        }

        private void DrawChartArea(Graphics g)
        {
            try
            {
                YMax = -float.MaxValue;
                YMin = float.MaxValue;
                foreach (IIndicator indicator in _lstIndicator)
                {                    
                      YMax = Math.Max(YMax, indicator.YMax);
                      YMin = Math.Min(YMin, indicator.YMin);
                }

                Pen pen = new Pen(new SolidBrush(AxisLineColor));
                Pen penHour = new Pen(new HatchBrush(HatchStyle.DashedHorizontal, _cHourLineColor));
                Pen penDay = new Pen(new HatchBrush(HatchStyle.HorizontalBrick, _cDayLineColor));
                Pen customPen = null;
                int iHeight = DrawHeight;
                double dCount = Math.Max(1.0, Math.Floor(iHeight / (double)DEFAULT_INTERVAL));
                double dInterval = Math.Max(0, (YMax - YMin) / dCount);                                                
                double dCurrent;                
                int iPosition;
                int iCount;
                int iXSkipCount;
                string strDateTimeFormat;
                string strDateSeparateFormat;
                string strDrawString;
                int iDataLength;
                SizeF sDateSize;
                DateTime dtCurrent;
                DateTime dtPrevious;
                BarCompressionEnum eCompression;

                iDataLength = _lstIndicator.Count == 0
                                    ? 0
                                    : _dsDataSource.GetLength(_lstIndicator[0].BaseStudy);

                eCompression = _lstIndicator.Count == 0
                                ? BarCompressionEnum.Min_1 
                                : _dsDataSource.GetData(_lstIndicator[0].BaseStudy) == null
                                    ? BarCompressionEnum.Min_1 
                                    : _dsDataSource.GetData(_lstIndicator[0].BaseStudy).BarCompression;

                //g.Clear(BackColor);

                g.FillRectangle(new SolidBrush(BackColor), new Rectangle(0, 0, Width, Height));

                g.DrawString(_lstIndicator.Count == 0
                                ? ""
                                : _lstIndicator[0].BaseStudy,
                             Font, new SolidBrush(AxisLineColor),
                             new Point(LeftPad + 2, TopPad + 2));
                // Calculate Interval
                dInterval = CalculateInterval(YMax, YMin, dCount);
                iCount = Math.Max(1, (int)Math.Floor((((dInterval * dCount) + YMin) - YMax) / dInterval / 2) - 1);
                dCurrent = (Math.Floor(YMin / dInterval) - iCount) * dInterval;
                dCurrent = (dCurrent > YMin) ? dCurrent - dInterval : dCurrent;
                _dMinValue = dCurrent;
                _dPixelValue = dInterval / (double)DEFAULT_INTERVAL;

                // Draw Axis Line
                if (ShowXAxis)
                    g.DrawLine(pen, new Point(0, Height - _iBottomPad), new Point(Width, Height - _iBottomPad));
                g.DrawLine(pen, new Point(Width - _iRightPad, 0), new Point(Width - _iRightPad, iHeight));
                dCurrent += dInterval;
                for (iPosition = DEFAULT_INTERVAL; iPosition < iHeight; dCurrent += dInterval, iPosition += DEFAULT_INTERVAL)
                {
                    g.DrawLine(pen, new Point(Width - _iRightPad, iHeight - iPosition), new Point(Width - _iRightPad + 3, iHeight - iPosition));                    
                    g.DrawString(dCurrent.ToString("0.00"), Font, new SolidBrush(AxisLineColor), new Point(Width - _iRightPad + 5, iHeight - iPosition - (int)(Font.Height / 2)));
                }

                switch (eCompression)
                {
                    case BarCompressionEnum.Tick:
                        strDateTimeFormat = "mm:ss";
                        strDateSeparateFormat = "HH:mm";
                        break;
                    case BarCompressionEnum.Daily:
                        strDateTimeFormat = "MM/dd";
                        strDateSeparateFormat = "yy/MM";
                        break;
                    default:
                        strDateTimeFormat = "HH:mm";
                        strDateSeparateFormat = "MM/dd";
                        break;
                }
                

                iDataLength = EndIndex - StartIndex + 1;
                if (_isShowHourLine)
                {

                    for (int i = 0; i < iDataLength; i++)
                    {
                        dtCurrent = _lstIndicator.Count == 0
                                        ?   DateTime.MinValue
                                        :   _dsDataSource.GetData(_lstIndicator[0].BaseStudy) == null
                                            ?   DateTime.MinValue
                                            :   _dsDataSource.GetData(_lstIndicator[0].BaseStudy).DateTime(i + StartIndex);

/*
                        if ((dtCurrent.Minute == 0) && (XInterval * (iDataLength - i - 1) < Width - RightPad))
                            g.DrawLine(penHour,
                                       new Point(XInterval * (iDataLength - i - 1), TopPad),
                                       new Point(XInterval * (iDataLength - i - 1), TopPad + iHeight));
 */
                        if ((dtCurrent.Minute == 0) && (XInterval * i < Width - RightPad))
                            g.DrawLine(penHour,
                                       new Point(XInterval * i, TopPad),
                                       new Point(XInterval * i, TopPad + iHeight));
                    }
                }

                if (_isShowDayLine)
                {

                    for (int i = 0; i < iDataLength - 1; i++)
                    {
                        dtCurrent = _lstIndicator.Count == 0
                                        ? DateTime.MinValue
                                        : _dsDataSource.GetData(_lstIndicator[0].BaseStudy) == null
                                            ? DateTime.MinValue
                                            : _dsDataSource.GetData(_lstIndicator[0].BaseStudy).DateTime(i + StartIndex);

                        dtPrevious = _lstIndicator.Count == 0
                                        ? DateTime.MinValue
                                        : _dsDataSource.GetData(_lstIndicator[0].BaseStudy) == null
                                            ? DateTime.MinValue
                                            : _dsDataSource.GetData(_lstIndicator[0].BaseStudy).DateTime(i + StartIndex + 1);

                        if ((dtCurrent.Day != dtPrevious.Day)  && (XInterval * i < Width - RightPad))
                            g.DrawLine(penDay,
                                       new Point(XInterval * i, TopPad),
                                       new Point(XInterval * i, TopPad + iHeight));
                    }
                }

                foreach (ConstantLine customLine in _lstCustomLine)
                {
                    g.DrawLine(new Pen(new SolidBrush(customLine.Color)),
                               new Point(LeftPad, ValueToPixel(0, customLine.Value)[1]),
                               new Point(LeftPad + DrawWidth, ValueToPixel(0, customLine.Value)[1]));
                }

                if (ShowXAxis == false)
                    return;

                sDateSize = _lstIndicator.Count == 0 
                                ? new SizeF(10, 10)
                                : _dsDataSource.GetData(_lstIndicator[0].BaseStudy) == null
                                    ? new SizeF(10, 10)
                                    : g.MeasureString(_dsDataSource.GetData(_lstIndicator[0].BaseStudy).DateTime().ToString(strDateTimeFormat), Font);

                iXSkipCount = (int)Math.Ceiling(sDateSize.Width / XInterval);
                dCount = DrawWidth / (XInterval * iXSkipCount);
                

                for (int i = 0; (i < dCount) && (i * iXSkipCount < iDataLength); i++)
                {
                    dtCurrent = _lstIndicator.Count == 0 
                                    ? DateTime.Now
                                    : _dsDataSource.GetData(_lstIndicator[0].BaseStudy) == null
                                        ? DateTime.Now
                                        : _dsDataSource.GetData(_lstIndicator[0].BaseStudy).DateTime(i * iXSkipCount - 1 + StartIndex);

                    dtPrevious = _lstIndicator.Count == 0
                                        ? DateTime.Now
                                        : _dsDataSource.GetData(_lstIndicator[0].BaseStudy) == null
                                            ? DateTime.Now
                                            : _dsDataSource.GetData(_lstIndicator[0].BaseStudy).DateTime(i * iXSkipCount - 2 + StartIndex);
                    g.DrawLine(pen,
                               new Point(iXSkipCount * i * XInterval, iHeight),
                               new Point(iXSkipCount * i * XInterval, iHeight + 5));
                    
                    if(dtCurrent == DateTime.MinValue)
                        continue;
                    strDrawString = dtCurrent.Day != dtPrevious.Day 
                                        ? dtCurrent.ToString(strDateSeparateFormat) 
                                        : dtCurrent.ToString(strDateTimeFormat);

                    g.DrawString(strDrawString, Font, new SolidBrush(AxisLineColor),
                                 new Point(iXSkipCount * i * XInterval, iHeight + 5));
                }

                if (_dValues != null)
                {
                    g.DrawString("C : " + _dValues[1] + " Time : " + _dsDataSource.GetData(_lstIndicator[0].BaseStudy).DateTime((int)_dValues[0]).ToString("MM/dd HH:mm") , Font, new SolidBrush(AxisLineColor), new Point(10, 10));
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message + "," + ex.StackTrace);
            }
        }
/*
        protected override void OnPaintBackground(PaintEventArgs e)
        {

            try
            {
                PaintChart();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message + "," + ex.StackTrace);
            }
 
        }
*/ 
        public void SetDataSource(IDataSource dsDataSource)
        {
            _dsDataSource = dsDataSource;                        
        }

        public void AddIndicator(IIndicator indicator)
        {
            _lstIndicator.Add(indicator);
            indicator.DataSource = _dsDataSource;
            indicator.ChartMeta = this;
        }

        public IIndicator GetIndicatior(int iIndex)
        {
            return (iIndex >= 0) || (iIndex < _lstIndicator.Count)
                        ?   _lstIndicator[iIndex]
                        :   null;
        }

        public List<IIndicator> GetIndicatiors()
        {
            return _lstIndicator;
        }

        public void PaintChart()
        {
            try
            {
                Graphics g;
                Graphics eg = CreateGraphics();

                if (Visible == false)
                    return;

                Bitmap bitmap = new Bitmap(DrawWidth, DrawHeight);
                g = Graphics.FromImage(bitmap);

                foreach (IIndicator indicator in _lstIndicator)
                    indicator.Calculate();
    
                DrawChartArea(eg);

                foreach (IIndicator indicator in _lstIndicator)
                {
                    indicator.StartIndex = StartIndex;
                    indicator.EndIndex = EndIndex;
                    indicator.Draw(g);
                }
                
                eg.DrawImage(bitmap, 0, 0);
                eg.Dispose();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message + "," + ex.StackTrace);
            }
        }
    }
}
